﻿////////////////////////////////////////////////////////////////////////////////////////////////
//
// Copyright © Yaroslavov Alexander 2010
//
// Contacts:
// Phone: +7(906)827-27-51
// Email: x-ronos@yandex.ru
//
/////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Moppet.Lapa
{
	/// <summary>
	/// Элемент (узел) дерева разбора.
	/// </summary>
	public class LpNode // Не надо делать из этой ноды структуру, а то скорость упадёт.
	{
		/// <summary>
		/// Соответствие. Это то, что нашёл парсер.
		/// Блок текста, который был проанализирован (разобран).
		/// </summary>
		public LpText Match;

		/// <summary>
		/// Остаток текста. Это то, что осталось разобрать (проанализировать далее).
		/// </summary>
		public LpText Rest;

		/// <summary>
		/// Дочерние блоки текста из которых состоит Match.
		/// </summary>
		public IEnumerable<LpNode> Children;

		/// <summary>
		/// Идентификатор узла.
		/// </summary>
		public string Id;


		/// <summary>
		/// Вспомогательный конструктор копирования.
		/// </summary>
		/// <param name="n">Результат разбора.</param>
		public LpNode(LpNode n)
		{
			Id    = n.Id;
			Match = n.Match;
			Rest  = n.Rest;
			Children = n.Children.Select(nn => new LpNode(nn)).ToArray();
		}

        /// <summary>
        /// Конструирует узел, который обозначает неудачное соответствие.
        /// Т.е. неудачную попытку синтаксического разбора.
        /// </summary>
        /// <param name="id">Идентификатор узла.</param>
        /// <param name="rest">Остаток текста.</param>
        public LpNode(string id, LpText rest)
        {
            Match = new LpText(rest.Source, rest.Index, -1);
            Rest = rest;
        }

        /// <summary>
        /// Неудачное соответствие, неудачная попытка синтаксического разбора.
        /// </summary>
        /// <param name="rest">Остаток текста.</param>
        public LpNode(LpText rest)
        {
            Match = new LpText(rest.Source, rest.Index, -1);
            Rest = rest;
        }

        #region Take

        /// <summary>
        /// Конструктор для формирования результата заданной длины.
        /// </summary>
        /// <param name="text">Исходный текст.</param>
        /// <param name="nTake">Сколько символов нужно взять.</param>
        /// <param name="id">Идентификатор узла.</param>
        /// <param name="children">Дочерние узлы.</param>
        public LpNode(LpText text, int nTake, string id, IEnumerable<LpNode> children)
        {
            Id = id;
            Children = children;
            Match = new LpText(text.Source, text.Index, nTake);
            Rest = new LpText(text.Source, text.Index + nTake, text.Length - nTake);
        }

        /// <summary>
        /// Конструктор для формирования результата заданной длины.
        /// </summary>
        /// <param name="text">Исходный текст.</param>
        /// <param name="nTake">Сколько символов нужно взять.</param>
        /// <param name="id">Идентификатор узла.</param>
        /// <param name="children">Дочерние узлы.</param>
        public LpNode(LpText text, int nTake, string id, params LpNode[] children)
        {
            Id       = id;
            Children = children;
            Match    = new LpText(text.Source, text.Index, nTake);
            Rest     = new LpText(text.Source, text.Index + nTake, text.Length - nTake);
        }

        /// <summary>
        /// Конструктор для формирования результата заданной длины.
        /// </summary>
        /// <param name="text">Исходный текст.</param>
        /// <param name="nTake">Сколько символов нужно взять.</param>
        /// <param name="id">Идентификатор узла.</param>
        public LpNode(LpText text, int nTake, string id = null)
        {
            Id = id;
            Match = new LpText(text.Source, text.Index, nTake);
            Rest = new LpText(text.Source, text.Index + nTake, text.Length - nTake);
        }

        #endregion Take

        /// <summary>
		/// Основной конструктор.
		/// </summary>
		/// <param name="match">Соответствие (блок разобранного текста).</param>
		/// <param name="rest">Оставшийся текст.</param>
		/// <param name="children">Дочерние блоки текста из которых состоит match.</param>
		public LpNode(LpText match, LpText rest, params LpNode[] children)
		{
			Match = match; Rest = rest; Children = children;
		}

		/// <summary>
		/// Основной конструктор.
		/// </summary>
		/// <param name="match">Соответствие (блок разобранного текста).</param>
		/// <param name="rest">Оставшийся текст.</param>
		/// <param name="children">Дочерние блоки текста из которых состоит match.</param>
		public LpNode(LpText match, LpText rest, IEnumerable<LpNode> children)
		{
			Match = match; Rest = rest; Children = children;
		}

		/// <summary>
		/// Основной конструктор.
		/// </summary>
		/// <param name="id">Идентификатор узла.</param>
		/// <param name="match">Соответствие (блок разобранного текста).</param>
		/// <param name="rest">Оставшийся текст.</param>
		/// <param name="children">Дочерние блоки текста из которых состоит match.</param>
		public LpNode(string id, LpText match, LpText rest, params LpNode[] children)
		{
			Id = id; Match = match; Rest = rest; Children = children;
		}
		
		/// <summary>
		/// Основной конструктор.
		/// </summary>
		/// <param name="id">Идентификатор узла.</param>
		/// <param name="match">Соответствие (блок разобранного текста).</param>
		/// <param name="rest">Оставшийся текст.</param>
		/// <param name="children">Дочерние блоки текста из которых состоит match.</param>
		public LpNode(string id, LpText match, LpText rest, IList<LpNode> children)
		{
			Id = id; Match = match; Rest = rest; Children = children;
		}

		/// <summary>
		/// Истина, если данный узел является успешным соответствием заданному шаблону текста.
		/// </summary>
		public bool Success { get { return Match.Length >= 0; } }
		
		/// <summary>
		/// Возвращает блок текста, который представляет данный узел.
		/// Если блок текста непустой, значит текст считается разобранным (проанализированным).
		/// </summary>
		/// <returns>Строка, всегда не null.</returns>
		public override string ToString() { return Match.ToString(); }

		/// <summary>
        /// Обёртывает node в другой узел, после чего node оказывается в массиве Children нового узла.
        /// Обёртывание обычно необходимо, чтобы сохранить идентификацию узла node.
		/// </summary>
		/// <param name="node">Некий результат.</param>
		/// <returns>Обёртка.</returns>
		public static LpNode Wrap(LpNode node)
		{
			return new LpNode(node.Match, node.Rest, node);
		}

		/// <summary>
		/// Объединяет предыдущий и следующий результат в один.
		/// </summary>
		/// <param name="prev">Предыдущее соответствие.</param>
		/// <param name="next">Следующее соответствие.</param>
		/// <returns>Объект класса.</returns>
		public static LpNode Concat(LpNode prev, LpNode next)
		{
			return new LpNode(new LpText(prev.Match.Source, prev.Match.Index, next.Rest.Index - prev.Match.Index), next.Rest, prev, next);
		}

		/// <summary>
		/// Объединяет предыдущий и следующий результат в один.
		/// </summary>
		/// <param name="id">Идентификатор результата (узла).</param>
		/// <param name="prev">Предыдущее соответствие.</param>
		/// <param name="next">Следующее соответствие.</param>
		/// <returns>Объект класса.</returns>
		public static LpNode Concat(string id, LpNode prev, LpNode next)
		{
			return new LpNode(id, new LpText(prev.Match.Source, prev.Match.Index, next.Rest.Index - prev.Match.Index), next.Rest, prev, next);
		}

		#region Node helpers

		/// <summary>
		/// Выбирает все узлы с заданным идентификатором.
		/// Узлы выбираются не только из списка nodes, но и у всех дочерних элементов.
		/// </summary>
		/// <param name="nodes">Узлы.</param>
		/// <param name="predicate">Ф-я поиска идентификатора.</param>
		/// <param name="goIntoTheDeep">Если узел не удовлетворяет условию predicate и у него есть дочерние элементы, тогда вызывается эта лямбда, разрешающая поиск в глубину.</param>
		/// <returns>Выборка.</returns>
		public static IEnumerable<LpNode> Select(IEnumerable<LpNode> nodes, Func<LpNode, bool> predicate, Func<LpNode, bool> goIntoTheDeep)
		{
			foreach (var cn in nodes)
			{
				bool find = predicate(cn);
				if (find)
					yield return cn;

				if (!find && cn.Match.Length > 0 && cn.Children != null && goIntoTheDeep(cn))
				{
					foreach (var cnn in Select(cn.Children, predicate, goIntoTheDeep))
						yield return cnn;
				}
			}
		}

		/// <summary>
		/// Выбирает все узлы с заданным идентификатором.
		/// </summary>
		/// <param name="predicate">Ф-я поиска идентификатора.</param>
		/// <param name="goIntoTheDeep">Если узел не удовлетворяет условию predicate и у него есть дочерние элементы, тогда вызывается эта лямбда, разрешающая поиск в глубину.</param>
		/// <returns>Выборка.</returns>
		public IEnumerable<LpNode> Select(Func<LpNode, bool> predicate, Func<LpNode, bool> goIntoTheDeep)
		{
			return LpNode.Select(new[] { this }, predicate, goIntoTheDeep);
		}


		/// <summary>
		/// Выбирает все узлы удовлетворяющие условию predicate.
		/// Поиск в глубину ведётся.
		/// </summary>
		/// <param name="predicate">Ф-я поиска идентификатора.</param>
		/// <returns>Выборка.</returns>
		public IEnumerable<LpNode> Select(Func<LpNode, bool> predicate)
		{
			return LpNode.Select(new[] { this }, predicate, no => true);
		}

		/// <summary>
		/// Выбирает все узлы с заданным идентификатором.
		/// </summary>
		/// <param name="id">Идентификатор.</param>
		/// <param name="goIntoTheDeep">Истина, если нужно искать вглубь.</param>
		/// <returns>Выборка.</returns>
		public IEnumerable<LpNode> Select(string id, bool goIntoTheDeep = true)
		{
			return LpNode.Select(new[] { this }, n => n.Id == id, no => goIntoTheDeep);
		}

		/// <summary>
		/// Выбирает первый узел с заданным идентификатором.
		/// </summary>
		/// <param name="predicate">Ф-я поиска узла.</param>
		/// <returns>Узел или null.</returns>
		public LpNode FirstOrDefault(Func<LpNode, bool> predicate)
		{
			return LpNode.Select(new[] { this }, predicate, no => true).FirstOrDefault();
		}

		/// <summary>
		/// Выбирает первый узел с заданным идентификатором.
		/// </summary>
		/// <param name="id">Идентификатор.</param>
		/// <param name="goIntoTheDeep">Истина, если нужно искать вглубь.</param>
		/// <returns>Узел или null.</returns>
		public LpNode FirstOrDefault(string id, bool goIntoTheDeep = true)
		{
			return LpNode.Select(new[] { this }, n => n.Id == id, no => goIntoTheDeep).FirstOrDefault();
		}

		#endregion // Node helpers
	}


}
